# @Time    : 2020/8/13 10:13
# @Author  : Del Fung
# @File    : requester.py
# @Software: PyCharm


# 对requests包进行二次封装，改造requests使他能够胜任数据驱动流程测试。
# 主要改造session，请求方式，入参 等模块
# 版本号：V0.1 requests封装

import asyncio
import logging
import requests
import requests.auth


class Requester(object):
    def __init__(self):
        """
        在第一次实例Requester模块时，先进行session的实例化以及请求方法的实例
        """
        logging.getLogger("requests").setLevel(logging.ERROR)  # 用于去除requests的多余log
        self._session()

    def _session(self):
        """
        创建一个新的session，等于说创建一个新的页面，
        请求方法字典支持get、post、put、delete、head、patch，每一次session刷新一定要刷新请求方法字典
        不处理cookie 等缓存数据请后续自行处理
        """
        self.SESSION = requests.Session()
        self.METHOD = {'GET': self.SESSION.get, 'POST': self.SESSION.post, 'PUT': self.SESSION.put,
                       'DELETE': self.SESSION.delete, 'HEAD': self.SESSION.head, 'PATCH': self.SESSION.patch}

    # TODO 完成请求封装，后续需要在解析case的时候限制可选参数的key值，保证程序不会报TypeError
    def request_control(self, method, url, new=False, data=None, json=None, **kwargs):
        """
            封装request 请求，使所有请求都在session中进行，当有新的session页时只需要传入new=True即可刷新session页面，
        刷新之后之前的session页面保存的cookie等信息也将被刷新。没有保存session功能，请不要随意调用该方法
        :param method: 请求方法，get post put 等这些，规定是大写
        :param url: 请求地址
        :param new: 是否为新页面 刷新session(可选) 默认为false
        :param data: data数据，(可选) 默认为空值
        :param json: json数据，（可选） 默认为空值
        :param kwargs:缺省参数 ，（可选） 包含
                params, headers, cookies, files,auth,timeout,
                allow_redirects, proxies,hooks, stream, verify, cert
                超过以上列出来的参数会提示 TYPEERROR
        :return: response 将response值返回出来
        """
        self._session() if new else None
        if method in ('GET', 'DELETE', 'HEAD'):
            return self.METHOD.get(method)(url, **kwargs)
        elif method == 'POST':
            return self.METHOD.get('POST')(url, data, json, **kwargs)
        else:
            return self.METHOD.get(method)(url, data, **kwargs)


class Tokens(object):
    def __init__(self, url, data):
        """
        初始化token信息
        :param url: 【必须】获取token的地址
        :param data: 【必须】 请求token数据的data信息内容要求 字典格式
        """
        self.URL = url  # token 获取地址
        self.DATA = data  # token数据方法
        self.TOKENS = list()

    async def _get_token_async(self, appinfo: list):
        """
        获取token，并且将token存入base_data值对应模块中
        :param  【必须】appinfo: 从config中读取的App信息 格式 [appcode,secret,appname]
        :return:
        """
        appcode = appinfo[0]
        secret = appinfo[1]
        client_auth = requests.auth.HTTPBasicAuth(appcode, secret)
        response = requests.post(self.URL, auth=client_auth, data=self.DATA)
        token_json = response.json()
        if 'error' not in token_json.keys():
            appname = appinfo[2]
            self.TOKENS.append([appname, token_json])

    def _get_token(self, appinfo: list):
        """
        获取token信息，非异步
        :param appinfo: 【必须】appinfo: 从config中读取的App信息 格式 [appcode,secret,appname]
        :return:
        """

        appcode = appinfo[0]
        secret = appinfo[1]
        client_auth = requests.auth.HTTPBasicAuth(appcode, secret)
        response = requests.post(self.URL, auth=client_auth, data=self.DATA)
        token_json = response.json()
        if 'error' not in token_json.keys():
            appname = appinfo[2]
            self.TOKENS.append([appname, token_json])

    def auth_tokens(self, appinfo: list):
        """
        获取Token 对外方法，如需获取只需要调用即可，通过配置文件以及基础数据来获取token信息，并更新到toke信息中
        :return:
        """
        auth_type = appinfo.pop(0)
        if auth_type == 'OAuth2' and len(appinfo) > 1:
            # 进行异步操作
            loop = asyncio.get_event_loop()
            tasks = self._task_assign(appinfo)
            loop.run_until_complete(asyncio.wait(tasks))
            loop.close()
        else:
            self._get_token(appinfo[0])
        return self.TOKENS

    def _task_assign(self, auth_app):
        """
        任务分配模块，创建多线程任务
        :return:
        """

        if len(auth_app) > 1:
            return list(map(self._assign, auth_app))
        else:
            raise KeyError(f'请检查config.yaml//Authorization/AuthApp的值是否为空, '
                           f'若无需获取Token信息请将config.yaml//Authorization/Type 设置为No Auth')

    def _assign(self, app):
        """
        分配模块将进行任务分配，为解决for循环，使用map，所以有该方法
        :param app:
        :return:
        """
        return self._get_token_async(app)
